package org.yanh.user.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.yanh.common.domain.dto.LoginFormDTO;
import org.yanh.common.enums.ResultCode;
import org.yanh.common.domain.Result;
import org.yanh.common.security.TokenConstant;
import org.yanh.common.enums.UserStatus;
import org.yanh.common.exception.CommonException;
import org.yanh.common.security.TokenUtil;
import org.yanh.common.security.SecurityUtil;
import org.yanh.user.domain.User;
import org.yanh.user.service.IUserService;
import reactor.core.publisher.Mono;

import java.util.Map;
@Tag(name="登录相关接口",description = "登录相关接口")
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
    private final TokenUtil jwtUtil;
    private final IUserService userService;
    /**
     * 登录认证
     *
     */
    @Operation(summary = "登录认证",description = "登录认证")
    @PostMapping("/token")
    public Result<?> login(@RequestBody LoginFormDTO loginDTO) {

        // 1.请求体中获取用户名密码
        String username  = loginDTO.getUsername();
        String password = loginDTO.getPassword();

        // 2.用户名和密码为空
        if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
            return Result.error(ResultCode.LOGIN_EMPTY_ERROR.getCode(), ResultCode.LOGIN_EMPTY_ERROR.getMessage());
        }
        // 用户名或手机号查询
        User user = userService.lambdaQuery().eq(User::getUsername, username).one();
        //用户是否存在
        if (user==null||! SecurityUtil.matchesPassword(password, user.getPassword()))
        {
            return Result.error(ResultCode.LOGIN_ERROR.getCode(), ResultCode.LOGIN_ERROR.getMessage());
        }
        // 校验是否禁用
        if (user.getStatus() == UserStatus.FROZEN) {
            return Result.error(ResultCode.LOGIN_STATUS_ERROR.getCode(), ResultCode.LOGIN_STATUS_ERROR.getMessage());
        }

        try {
            // 通过 jwtTokenUtil 生成 JWT 令牌和刷新令牌
            Map<String, Object> tokenMap = jwtUtil
                    .generateTokenAndRefreshToken(user.getId().toString(), username);
            return Result.success(tokenMap);
        } catch(CommonException e) {
            return Result.error(ResultCode.UNKNOWN_ERROR.getCode(), ResultCode.UNKNOWN_ERROR.getMessage());
        }
    }

    /**
     * 刷新JWT令牌,用旧的令牌换新的令牌
     * 参数为需要刷新的令牌
     * header中携带刷新令牌
     */
    @Operation(summary = "刷新JWT令牌",description = "刷新JWT令牌")
    @GetMapping("/refresh")
    public Mono<Result> refreshToken(@RequestHeader(value = TokenConstant.RefreshHeader) String token){

        token = jwtUtil.replaceTokenPrefix(token);
        if (org.apache.commons.lang3.StringUtils.isEmpty(token)) {
            return buildErrorResponse(ResultCode.TOKEN_MISSION);
        }

        // 对Token解签名，并验证Token是否过期
        boolean isJwtNotValid = jwtUtil.isTokenExpired(token);
        if(isJwtNotValid){
            return buildErrorResponse(ResultCode.TOKEN_INVALID);
        }

        String userId = jwtUtil.getUserIdFromToken(token);
        String username = jwtUtil.getUserNameFromToken(token);

        // 这里为了保证 refreshToken 只能用一次，刷新后，会从 redis 中删除。
        // 如果用的不是 redis 中的 refreshToken 进行刷新令牌，则不能刷新。
        // 如果使用 redis 中已过期的 refreshToken 也不能刷新令牌。
        boolean isRefreshTokenNotExisted = jwtUtil.isRefreshTokenNotExistCache(token);
        if(isRefreshTokenNotExisted){
            return buildErrorResponse(ResultCode.REFRESH_TOKEN_INVALID);
        }

        Map<String, Object> tokenMap = jwtUtil.refreshTokenAndGenerateToken(userId, username);

        return buildSuccessResponse(tokenMap);

    }

    /**
     * 登出，删除 redis 中的 accessToken 和 refreshToken
     * 只保证 refreshToken 不能使用，accessToken 还是能使用的。
     * 如果用户拿到了之前的 accessToken，则可以一直使用到过期，但是因为 refreshToken 已经无法使用了，所以保证了 accessToken 的时效性。
     * 下次登录时，需要重新获取新的 accessToken 和 refreshToken，这样才能利用 refreshToken 进行续期。
     */
    @Operation(summary = "登出",description = "登出")
    @Parameters(value = {
            @Parameter(name = "userId", description = "userId", example = "1")
    })
    @PostMapping("/logout")
    public  Mono<Result> logout(@RequestParam("userId") String userId){

        boolean logoutResult = jwtUtil.removeToken(userId);

        if (logoutResult) {
            buildSuccessResponse(ResultCode.SUCCESS);
        } else {
            buildErrorResponse(ResultCode.LOGOUT_ERROR);
        }

        return buildSuccessResponse(ResultCode.SUCCESS);
    }

    private Mono<Result> buildErrorResponse(ResultCode responseCodeEnum){
        return Mono.create(callback -> callback.success(
                Result.error(responseCodeEnum.getCode(), responseCodeEnum.getMessage())
        ));
    }

    private Mono<Result> buildSuccessResponse(Object data){
        return Mono.create(callback -> callback.success(Result.success(data)
        ));
    }
}
